package file_safeBox

//保险箱
import (
	"errors"
	"fmt"
	"github.com/goccy/go-json"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/os/glog"
	"github.com/mojocn/base64Captcha"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/core/middleware"
	//"gitlab.local/TerraMaster/tos-modules/middleware"
	"gitlab.local/golibrary/user"
	"gitlab.local/golibrary/utils"
	"golang.org/x/sys/unix"
	"image/color"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	SafeBoxDir      = "%s/.safe"      //保险箱目录
	MountSafeBoxDir = "%s/safe"       //保险箱挂载目录
	OutSafeBoxDir   = "%s/safe_out"   //保险箱文件输出目录
	safeKey         = "user.safe.key" //保险箱秘钥key
)

type noCopy struct{}

func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type BoxRecord struct {
	Count int       `json:"count"` //尝试登录次数
	Time  time.Time `json:"time"`  //上次尝试时间
}

type SafeBoxIn interface {
	SafeBoxUnLock(safeBoxDir string) error
	SafeBoxLock(safeBoxDir string) error
	SetSafeBoxPwd(uname, pwd string) error
	GetSafeBoxConf(uname string) (bool, bool, error)
	CloseSafeBox(uname string, force bool) (int, error)
	OpenSafeBox(uname string, pwd string, image string, imageId string) (interface{}, error)
	GetSafeBoxPath(uname string) string
}

type SafeBox struct {
	noCopy noCopy
	sync.Mutex
	safeBoxLockCount map[string]int32       //保险箱锁计数器，为0上锁,为1解锁
	captcha          *base64Captcha.Captcha //验证码处理服务
	MiddleWare       middleware.AuthChecker //中间件服务
}

// SafeBoxCfg 保险箱配置
type SafeBoxCfg struct {
	Status   bool   `json:"status"` //是否开启
	Password string `json:"pass_word"`
}

func New(m middleware.AuthChecker) *SafeBox {
	return &SafeBox{
		safeBoxLockCount: make(map[string]int32, 0),
		captcha:          base64Captcha.NewCaptcha(base64Captcha.NewDriverString(30, 120, 0, base64Captcha.OptionShowHollowLine, 4, base64Captcha.TxtNumbers, &color.RGBA{R: 254, G: 254, B: 254, A: 254}, &base64Captcha.EmbeddedFontsStorage{}, []string{}), base64Captcha.DefaultMemStore),
		MiddleWare:       m,
	}
}

// SafeBoxUnLock 保险箱解锁
func (s *SafeBox) SafeBoxUnLock(safeBoxDir string) error {
	s.Lock()
	defer s.Unlock()
	suffix := strings.TrimSuffix(safeBoxDir, "/")
	if _, b := s.safeBoxLockCount[suffix]; !b {
		s.safeBoxLockCount[suffix] = 0
	}
	s.safeBoxLockCount[suffix]++
	if s.safeBoxLockCount[suffix] == 1 {
		return s.unLock(safeBoxDir)
	}
	return nil
}

// SafeBoxLock 保险箱加锁
func (s *SafeBox) SafeBoxLock(safeBoxDir string) error {
	s.Lock()
	defer s.Unlock()
	suffix := strings.TrimSuffix(safeBoxDir, "/")
	if _, b := s.safeBoxLockCount[suffix]; !b {
		s.safeBoxLockCount[suffix] = 0
	}
	s.safeBoxLockCount[suffix]--
	if s.safeBoxLockCount[suffix] == 0 {
		return s.lock(safeBoxDir)
	}
	if s.safeBoxLockCount[suffix] < 0 {
		s.safeBoxLockCount[suffix] = 0
	}
	return nil
}

// SetSafeBoxPwd 设置保险箱密码
func (s *SafeBox) SetSafeBoxPwd(uname, pwd string) error {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return err
	}

	safeBoxDir := fmt.Sprintf(SafeBoxDir, lookup.HomeDir)

	safePwd := s.getPwd(safeBoxDir)
	if safePwd != "" {
		//要是密码存在则表示已经创建过保险箱，不能在创建了
		return errors.New("the safe already exists")
	}

	key, err := s.enKey(pwd)
	if err != nil {
		return gerror.Wrap(err, "SetSafeBoxPwd2")
	}

	uid, _ := strconv.Atoi(lookup.Uid)
	gid, _ := strconv.Atoi(lookup.Gid)

	//创建保险箱文件夹

	if !utils.Exists(safeBoxDir) {
		if e := utils.SwitchUserRight(uname); e != nil {
			return gerror.Wrap(err, "SetSafeBoxPwd4-1")
		}

		cmdStr := fmt.Sprintf("mkdir %s", safeBoxDir)
		if _, err = utils.ShellUserExec(uname, cmdStr); err != nil {
			return gerror.Wrap(err, "SetSafeBoxPwd4")
		}
		_ = os.Chown(safeBoxDir, uid, gid)
	}

	mountSafeBoxDir := fmt.Sprintf(MountSafeBoxDir, lookup.HomeDir)
	if !utils.Exists(mountSafeBoxDir) {
		if err = os.Mkdir(mountSafeBoxDir, 0700); err != nil {
			return gerror.Wrap(err, "SetSafeBoxPwd5")
		}
		_ = os.Chown(mountSafeBoxDir, uid, gid)
	}

	//挂载保险箱
	if err = s.mount(safeBoxDir, mountSafeBoxDir, key); err != nil {
		return gerror.Wrap(err, "SetSafeBoxPwd6")
	}

	//保存key
	if err = s.SetPWD(safeBoxDir, key); err != nil {
		return gerror.Wrap(err, "SetSafeBoxPwd7")
	}

	return nil
}

func (s *SafeBox) SetPWD(path, val string) error {
	_, err := utils.ShellExec(fmt.Sprintf(`setfattr -n '%s' -v '%s' '%s'`, safeKey, val, path))
	return err
}

// Lock 锁定指定路径
func (s *SafeBox) lock(path string) error {
	_, err := utils.ShellExec(fmt.Sprintf(`chattr +i '%s'`, path))
	return err
}

// UnLock 解锁指定路径
func (s *SafeBox) unLock(path string) error {
	_, err := utils.ShellExec(fmt.Sprintf(`chattr -i '%s'`, path))
	return err
}

// GetSafeBoxConf 保险箱配置
// return 是否开启 是否设置密码 错误
func (s *SafeBox) GetSafeBoxConf(uname string) (bool, bool, string, error) {
	//判断保险箱目录是否存在，如果存在说明设置过保险箱（已设置过密码）
	//判断保险箱是否已挂载，已挂载则表示开启
	lookup, err := user.Lookup(uname)
	path := fmt.Sprintf(MountSafeBoxDir, lookup.HomeDir)
	if err != nil {
		return false, false, path, err
	}
	status := s.IsMount(path)
	isPasswd := s.getPwd(fmt.Sprintf(SafeBoxDir, lookup.HomeDir)) != ""

	return status, isPasswd, path, nil
}

// forceCloseSafeBox 强制关闭保险箱(先阻止io，在卸载)
func (s *SafeBox) forceCloseSafeBox(toPath string) error {
	res, err := utils.ShellExec(fmt.Sprintf(`fuser -m '%s'`, toPath))
	if err != nil {
		return err
	}
	getpid := os.Getpid()
	res = strings.Replace(res, strconv.Itoa(getpid), "", 1) //排除本身
	res = strings.TrimSpace(res)
	if res != "" {
		for _, v := range strings.Fields(res) {
			atoi, err := strconv.Atoi(v)
			if err != nil {
				continue
			}
			_ = unix.Kill(atoi, unix.SIGKILL)
		}
	}
	_ = s.lock(toPath)
	time.Sleep(time.Second)
	if err = s.umount(toPath); err != nil {
		return err
	}
	_ = os.Remove(toPath)
	return nil
}

// closeSafeBox 关闭保险箱
func (s *SafeBox) closeSafeBox(toPath, safeBoxDir string) error {
	if err := s.umount(toPath); err != nil {
		return err
	}
	_ = os.Remove(toPath)
	_ = s.lock(safeBoxDir)
	return nil
}

// CloseSafeBox 关闭保险箱
// return 1.保险箱被占用
func (s *SafeBox) CloseSafeBox(uname string, force bool) (int, error) {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return 0, err
	}
	mountSafeBoxDir := fmt.Sprintf(MountSafeBoxDir, lookup.HomeDir)
	if force {
		return 0, s.forceCloseSafeBox(mountSafeBoxDir)
	}
	safeBoxDir := fmt.Sprintf(SafeBoxDir, lookup.HomeDir)
	if err = s.closeSafeBox(mountSafeBoxDir, safeBoxDir); err != nil {
		glog.Error(err, "CloseSafeBox")
		return 1, nil
	}
	return 0, nil
}

// OpenSafeBox 开启保险箱
// return 1未设置密码,2密码错误 错误 3验证码错误 4 密码加密失败 5 创建挂载目录失败
func (s *SafeBox) OpenSafeBox(uname, pwd, image, imageId string) (interface{}, error) {

	rsp := struct {
		Code    int    `json:"code"`
		ImageId string `json:"image_id"`
		Image   string `json:"image"`
		Path    string `json:"path"`
	}{}
	lookup, err := user.Lookup(uname)
	if err != nil {
		return rsp, err
	}

	mountSafeBoxDir := fmt.Sprintf(MountSafeBoxDir, lookup.HomeDir)
	safeBoxDir := fmt.Sprintf(SafeBoxDir, lookup.HomeDir)

	_ = s.unLock(SafeBoxDir)

	//验证提交的验证码
	if image != "" {
		if !s.captcha.Verify(imageId, image, true) {
			rsp.Code = 3
			return rsp, nil
		}
	}

	getPwd := s.getPwd(safeBoxDir)
	if getPwd == "" {
		rsp.Code = 1
		return rsp, nil
	}

	if !utils.Exists(mountSafeBoxDir) {
		if err = os.Mkdir(mountSafeBoxDir, 0700); err != nil {
			rsp.Code = 5
			return rsp, nil
		}
	}

	key, err := s.enKey(pwd)
	if err != nil {
		rsp.Code = 4
		return rsp, err
	}
	rec := recordJson(uname)
	if getPwd != key {
		//如果时间是零值
		if rec.Time.IsZero() {
			rec.Time = time.Now().Add(time.Duration(3) * time.Minute)
			rec.Count++
		} else {
			//如果设置时间还没过期
			if rec.Time.After(time.Now()) {
				rec.Count++
			} else {
				//重置时间，3分钟
				rec.Time = time.Now().Add(time.Duration(3) * time.Minute)
				//rec.Count++
				rec.Count = 0
			}
		}
		_ = saveRecordJson(uname, rec)

		if rec.Count > 3 {
			rsp.ImageId, rsp.Image, _, _ = s.captcha.Generate()
		}
		//密码错误
		rsp.Code = 2
		return rsp, nil
	}

	rec.Time = time.Time{}
	rec.Count = 0
	rsp.Path = mountSafeBoxDir
	_ = saveRecordJson(uname, rec)
	_ = s.unLock(safeBoxDir)
	return rsp, s.mount(safeBoxDir, mountSafeBoxDir, key)
}

func saveRecordJson(uname string, record BoxRecord) error {
	rsFile := filepath.Join(constant.AccountRecordDir, uname)

	byteData, err := json.MarshalIndent(record, "", " ")
	if err != nil {
		return errors.New("failed to json parse error")
	}

	saveErr := os.WriteFile(rsFile, byteData, 0644) // 写入配置文件
	if saveErr != nil {
		return errors.New("failed to save data error")
	}
	return nil
}

func recordJson(uname string) (result BoxRecord) {
	result = BoxRecord{}
	rsFile := filepath.Join(constant.AccountRecordDir, uname)
	body, err := os.ReadFile(rsFile)
	if err != nil {
		return
	}
	err = json.Unmarshal(body, &result)
	if err != nil {
		return
	}
	return
}

// GetSafeBoxPath 获取保险箱path
// return 为空，可能未开启保险箱或者用户不存在
func (s *SafeBox) GetSafeBoxPath(uname string) string {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return ""
	}
	mountSafeBoxDir := fmt.Sprintf(MountSafeBoxDir, lookup.HomeDir)
	if s.IsMount(mountSafeBoxDir) {
		abs, _ := filepath.Abs(mountSafeBoxDir)
		return abs
	}
	return ""
}

func (s *SafeBox) getPwd(path string) string {
	res, err := utils.ShellExec(fmt.Sprintf(`getfattr -n '%s' '%s'`, safeKey, path))
	if err != nil {
		return ""
	}
	res = strings.TrimSpace(res)
	if res == "" {
		return ""
	}
	for _, v := range strings.Split(res, "\n") {
		const prefix = safeKey + "="
		if strings.HasPrefix(v, prefix) {
			return strings.Trim(strings.TrimPrefix(v, prefix), `"`)
		}
	}
	return ""
}

func (s *SafeBox) setPwd(path, val string) error {
	_, err := utils.ShellExec(fmt.Sprintf(`setfattr -n '%s' -v '%s' '%s'`, safeKey, val, path))
	return err
}

// ResetSafeBox
// @Description: 重置保险箱。
// @receiver s
// @param path
// @param val
// @param uname
// @return error
func (s *SafeBox) ResetSafeBox(uname string) error {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return err
	}
	safeBoxDir := fmt.Sprintf(SafeBoxDir, lookup.HomeDir)
	mountSafeBoxDir := fmt.Sprintf(MountSafeBoxDir, lookup.HomeDir)
	if err = s.umount(mountSafeBoxDir); err != nil {
		return err
	}
	//删除保险箱文件
	_ = s.unLock(safeBoxDir)
	_, err = utils.ShellExec(fmt.Sprintf(`rm -rf %s`, safeBoxDir))
	return err
}

func (s *SafeBox) ResetVerificationCode() interface{} {
	rsp := struct {
		ImageId string `json:"image_id"`
		Image   string `json:"image"`
	}{}
	rsp.ImageId, rsp.Image, _, _ = s.captcha.Generate()
	return rsp
}
